#include<assert.h>
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>

#include "utils.h"
#include "p2p-file.h"
#include "sha1.h"
#include "p2p-config.h"
#include "my_log.h"

 
/*  Read file. Accepts a file name, position in file from which to copy from, 
    a buffer to store the copied data and length. If length is -1 it reads
    the entire file.
*/
int read_file(const char *file_path, int start_pos, char **buffer, int length)
{
    int fd;
    int bytes_read = 0;
    struct stat stbuf;

    fd = open(file_path, O_RDONLY);

    if (fd < 0) {
        return -1;
    }

    lseek(fd, start_pos, SEEK_SET);
    
    if (length == -1) {
        fstat(fd, &stbuf);
        length = stbuf.st_size;
    }
  
    *buffer = (char *)malloc(length * sizeof(char));
    assert(*buffer != NULL);
    
    if((bytes_read = read(fd, *buffer, length)) < 0)
        perror("read failed\n");

    close(fd);

    return bytes_read;
}

/* Generate a file of concatenated hashes of all the pieces of a file
*/
int generate_hash_file(const char *file_path)
{
    char *buffer;
    char hash[SHA1_HASH_SIZE];
    char hash_file_path[256];
    int i, piece_no = 0, fd, bytes_read, bytes_written, errno, errsv;
    FILE *fp;

    //Generate a file name for the hash file
    sprintf(hash_file_path, "%s.%s", file_path, "hash");
    fp = fopen(hash_file_path, "wb+");
    errsv = errno;

    if (fp < 0) {
        printf("[p2p-file] Error opening file: %s\n", hash_file_path);
        PRINT_TOKEN(errsv);
        return -1;
    }
    
    do {
        if ((bytes_read = read_file(file_path, piece_no * PIECE_SIZE, &buffer, PIECE_SIZE)) == -1) {
            printf("[p2p-file] read_file returned error: %s\n", file_path);
            return -1;
        }

        LOG(LOG_INFO, "Computing hash for Piece #%d. Piece size:%d", piece_no, bytes_read);

        if (compute_hash(buffer, PIECE_SIZE, hash) == -1) {
            LOG(LOG_ERR, "compute_hash failed");
            free(buffer);
            return -1;
        }

        free(buffer);
        
        bytes_written += fwrite(hash, 1, SHA1_HASH_SIZE, fp);
        fseek(fp, bytes_written, SEEK_SET);
        piece_no++;
    } while (bytes_read >= PIECE_SIZE);

    fclose(fp);
    return 0;
}

int write_file(const char *file_path, int start_pos, const char *buffer, int length, int flags)
{
    int fd, bytes_written, errsv;

    fd = open(file_path, flags, S_IRUSR | S_IWUSR | S_IXUSR);
    errsv = errno;

    if (fd < 0) {
        PRINT_TOKEN(errsv);
        return -1;
    }

    if (length == -1)
        length = sizeof(buffer);
    
    lseek(fd, start_pos, SEEK_SET);
    
    if((bytes_written = write(fd, buffer, length)) < 0) {
        perror("write failed\n");
        return -1;
    }   

    close(fd);
    return 0;
}

int verify_piece_integrity(const char *file, const char *hash_file, uint32_t index, int no_of_pieces)
{
    char *buffer1, *buffer2, hash[SHA1_HASH_SIZE];
    int br, return_val = 1;

    if ((br = read_file(file, index * PIECE_SIZE, &buffer1, PIECE_SIZE)) == -1) {
        LOG(LOG_ERR, "File: %s not found", file);
        free(buffer1);
        return -1;
    } else if (br < PIECE_SIZE) {
        if (index != no_of_pieces - 1) {
            LOG(LOG_ERR, "Piece size is less than PIECE_SIZE and this is not last piece");
            free(buffer1);
            return -1;
        }
    }
    
    LOG(LOG_INFO, "Computing hash for Piece #%d. Piece size:%d", index, br);

    if (compute_hash(buffer1, PIECE_SIZE, hash) == -1) {
        LOG(LOG_ERR, "compute_hash failed");
        return_val = -1;
    }

    if ((br = read_file(hash_file, index * SHA1_HASH_SIZE, &buffer2, SHA1_HASH_SIZE)) == -1) {
        LOG(LOG_ERR, "File: %s not found", file);
        return_val = -1;
    } else if (br < SHA1_HASH_SIZE) {
        LOG(LOG_ERR, "Hash size is less than SHA1_HASH_SIZE");
        return_val = -1;
    }
    
    if (!memcmp(buffer1, buffer2, SHA1_HASH_SIZE)) {
        LOG(LOG_ERR, "Hash mismatch. Piece integrity test failed");
        return_val = -1;
    }

    free(buffer1);
    free(buffer2);
    return return_val;
}

int compute_hash(const char *buffer, int size, char *hash)
{
    int i;
    char *hash_part = (char *)malloc((SHA1_PART_HASH_SIZE + 1) * sizeof(char));
    SHA1Context sha;
    
    SHA1Reset(&sha);
    SHA1Input(&sha, buffer, size);

    if (!SHA1Result(&sha)) {
        LOG(LOG_ERR, "Could not compute message digest");
        return -1;
    } else {
        // Concatenate all the "part-hashes"
        for(i = 0; i < 5; i++) {
            sprintf(hash_part, "%X", sha.Message_Digest[i]);
            memcpy(hash + SHA1_PART_HASH_SIZE * i, hash_part, SHA1_PART_HASH_SIZE);
        }
    }

    free(hash_part);
    return 0;
}
